﻿using Abp.Dependency;
using Abp.Domain.Repositories;
using Abp.Domain.Uow;
using Abp.Extensions;
using Abp.Json;
using Abp.MultiTenancy;
using Microsoft.EntityFrameworkCore;
using Mt.Site.Core.AccessStatistics.BackgroundJob;
using Mt.Site.Core.AccessStatistics.Entities;
using Mt.Site.Core.Navigations;
using Mt.Site.Core.Url;
using System;
using System.Linq;
using System.Threading.Tasks;
using UAParser.FormFactor.Models;

namespace Mt.Site.Core.AccessStatistics
{
    public class DataProcessor : IDataProcessor, ITransientDependency
    {
        private readonly IRepository<AccessData, long> _accessDataRepository;
        private readonly IRepository<AccessGeneral> _accessGeneralRepository;
        private readonly IIpDataManager _ipDataManager;
        private readonly IUserAgentParser _userAgentParser;
        private readonly IUnitOfWorkManager _unitOfWorkManager;
        private readonly IMenuCache _menuCache;
        private readonly ITenantStore _tenantStore;
        private readonly IRouteDataChecker _routeDataChecker;

        //private const int MinSpan = 100; //毫秒，一个访客两个连续页面间的最小间隔（防常规的连续刷新）
        private const int MaxFreeTime = 30; //分，两个连续页面间的间隔时间大于30分时，划分为两次会话。

        public DataProcessor(
            IRepository<AccessData, long> accessDataRepository,
            IRepository<AccessGeneral> accessGeneralRepository,
            IIpDataManager ipDataManager,
            IUserAgentParser userAgentParser,
            IUnitOfWorkManager unitOfWorkManager,
            IMenuCache menuCache,
            ITenantStore tenantStore,
            IRouteDataChecker routeDataChecker)
        {
            _accessDataRepository = accessDataRepository;
            _accessGeneralRepository = accessGeneralRepository;
            _ipDataManager = ipDataManager;
            _userAgentParser = userAgentParser;
            _unitOfWorkManager = unitOfWorkManager;
            _menuCache = menuCache;
            _tenantStore = tenantStore;
            _routeDataChecker = routeDataChecker;
        }

        public async Task Process(VisitorArgs args)
        {
            //检查ip库数据是否已导入
            _ipDataManager.CheckIpRepository();

            /*
                可能存在多个分站，但访客的页面浏览是可能跨分站的。
                1. 所以在统计数据也要混在一起统计，除了具体的内容统计（栏目统计）时需要按分站来分组
             */
            using (var uow = _unitOfWorkManager.Begin())
            {
                var client = _userAgentParser.Parse(args.UserAgent);
                if (!client.Device.IsSpider)
                {
                    var account = args.HMAccount;

                    // 由数据库中查找该访客以前访问的最后一个页面
                    var preData = await _accessDataRepository
                        .Query(q => q.Where(c => c.Acount == account && c.VisitTime < args.VisitTime).OrderByDescending(c => c.VisitTime))
                        .FirstOrDefaultAsync();

                    // 如果服务器不稳定或其他原因导致任务执行失败，可能打乱数据的顺序
                    var nextData = await _accessDataRepository
                        .Query(q => q.Where(c => c.Acount == account && c.VisitTime >= args.VisitTime).OrderBy(c => c.VisitTime))
                        .FirstOrDefaultAsync();

                    bool IsNewSession = true;
                    int lengthOfStay = 0;
                    if (preData != null)
                    {
                        var span = args.VisitTime.Subtract(preData.VisitTime);
                        //if (span.TotalMilliseconds < MinSpan)
                        //{
                        //    return; //无效访问（不拦截了，因为有些人的访问习惯就是一次打开若干页面）
                        //}

                        if (span.TotalMinutes < MaxFreeTime)
                        {
                            IsNewSession = false;
                            //前一个页面的停留时长
                            lengthOfStay += CompletePreData(preData, span);
                        }
                    }

                    AccessData accessData = await TranslateFromArgs(args, client);
                    if (IsNewSession)
                    {
                        accessData.SessionId = Guid.NewGuid().ToString("N");
                        accessData.IsFistPage = 1;   //新会话的第一个页面
                        accessData.IsBounced = 1;    //跳出页（本次会话只访问了一个页面）
                        accessData.IsEndPage = 1;    //是本次会话的最后一个页面
                        if (preData == null || preData.VisitTime.Day != accessData.VisitTime.Day)
                        {
                            accessData.FrequentVisits = 1;  //今天的第一次会话
                            accessData.LastVisitTime = accessData.VisitTime;
                        }
                        else
                        {
                            accessData.FrequentVisits = preData.FrequentVisits + 1;    //今日会话次数
                            accessData.LastVisitTime = preData.FirstPageVisitTime;     //上一次访问的时间
                        }
                        accessData.TimeTotol = 0;              //在线时间
                        accessData.PageNum = 1;                //访问页面数

                        accessData.FirstPageUrl = accessData.Url;
                        accessData.FirstPageVisitTime = accessData.VisitTime;

                        GetInfoFromRefererUrl(accessData);

                        var ipInfp = _ipDataManager.GetIPInfo(args.ClientIp);
                        accessData.ISP = ipInfp.ISP;
                        accessData.Region = ipInfp.Region;
                        accessData.City = ipInfp.City;

                        accessData.IsSecondPage = false;
                    }
                    else
                    {
                        accessData.SessionId = preData.SessionId;
                        accessData.IsFistPage = 0;
                        accessData.IsBounced = 0;
                        accessData.IsEndPage = 1;
                        accessData.TimeTotol = preData.TimeTotol;
                        accessData.PageNum = preData.PageNum + 1;

                        if (preData.IsFistPage  == 1)
                        {
                            accessData.IsSecondPage = true;
                        }
                        else
                        {
                            accessData.IsSecondPage = false;
                        }

                        CopyDataFromPreData(preData, accessData);
                    }

                    int minus = 0;  //统计时，第一个页面会为总跳出量+1，如果存在第二个页面，要减一。

                    if (nextData != null)
                    {
                        if (preData != null)
                        {
                            var span = nextData.VisitTime.Subtract(args.VisitTime);
                            if (span.TotalMinutes < MaxFreeTime)  //本次打开的页面可以nextData对应的页面归入同一次会话
                            {
                                bool isSameSession = preData.SessionId == nextData.SessionId;
                                if (isSameSession)
                                {
                                    CompletePreData(accessData, span);
                                    lengthOfStay = 0;  //此前的统计中已经把它算在了总和里
                                    var span22 = nextData.VisitTime.Subtract(preData.VisitTime).TotalMilliseconds.To<int>();
                                    preData.TimeTotol = preData.TimeTotol - span22;
                                    accessData.TimeTotol = accessData.TimeTotol - span22;
                                }
                                else
                                {
                                    lengthOfStay += CompletePreData(accessData, span);
                                }
                                var list = _accessDataRepository.GetAllList(c => c.SessionId == nextData.SessionId).OrderBy(c => c.VisitTime).ToList();
                                minus = GetMinusCount(preData, nextData, accessData, minus, list.Count);

                                for (int i = 0; i < list.Count; i++)
                                {
                                    var item = list[i];
                                    if (i == 0 && accessData.IsFistPage == 1)
                                    {
                                        item.IsSecondPage = true;
                                    }
                                    else
                                    {
                                        item.IsSecondPage = false;
                                    }

                                    item.SessionId = accessData.SessionId;
                                    item.IsFistPage = 0;
                                    item.IsBounced = 0;
                                    if (!isSameSession)
                                    {
                                        item.TimeTotol = item.TimeTotol + lengthOfStay;
                                        CopyDataFromPreData(accessData, item);
                                    }
                                    item.PageNum = item.PageNum + 1;
                                }
                            }
                            else
                            {
                                //FrequentVisits、LastVisitTime 可能有误，但不做调整了
                                if (accessData.IsSecondPage)
                                {
                                    minus = 1;
                                }
                            }
                        }
                        else
                        {
                            var span = nextData.VisitTime.Subtract(args.VisitTime);
                            if (span.TotalMinutes < MaxFreeTime)  //本次打开的页面可以nextData对应的页面归入同一次会话
                            {
                                accessData.SessionId = nextData.SessionId;
                                var list = _accessDataRepository.GetAllList(c => c.SessionId == nextData.SessionId).OrderBy(c => c.VisitTime).ToList();
                                if(list.Count == 1)
                                {
                                    minus = 1;   //nextData不再是跳出页
                                }
                                lengthOfStay = CompletePreData(accessData, span);
                                for (int i = 0; i < list.Count; i++)
                                {
                                    var item = list[i];
                                    if (i == 0)
                                    {
                                        item.IsSecondPage = true;
                                    }
                                    else
                                    {
                                        item.IsSecondPage = false;
                                    }

                                    item.IsFistPage = 0;
                                    item.IsBounced = 0;
                                    item.TimeTotol = item.TimeTotol + lengthOfStay;
                                    CopyDataFromPreData(accessData, item);
                                    item.PageNum = item.PageNum + 1;
                                }
                            }
                        }
                    }
                    else
                    {
                        if (accessData.IsSecondPage)
                        {
                            minus = 1;  
                        }
                    }

                    _accessDataRepository.Insert(accessData);

                    //汇总数据
                    UpdateGeneralData(preData, accessData, minus, lengthOfStay);
                    _unitOfWorkManager.Current.SaveChanges();
                    uow.Complete();
                }
            }
        }

        private static int GetMinusCount(AccessData preData, AccessData nextData, AccessData accessData, int minus, int count)
        {
            if (preData.SessionId != nextData.SessionId)
            {
                if (accessData.IsFistPage == 1)  //与前一个页面不是同一次会话, 但与后一个页面属同一次会话
                {
                    if (count == 1)     // 后一个页面原来是跳出页
                    {
                        minus = 1;
                    }
                }
                else if (accessData.IsSecondPage)
                {
                    if (count == 1)
                    {
                        minus = 2; //否定了preData、nextData是跳出页
                    }
                    else
                    {
                        minus = 1; //否定了preData是跳出页
                    }
                }
                else
                {
                    if (count == 1)
                    {
                        minus = 1; //否定了nextData是跳出页
                    }
                }
            }
            else
            {
                minus = 0;
            }

            return minus;
        }

        private void UpdateGeneralData(AccessData preData, AccessData accessData, int minus, int lengthOfStay)
        {
            int span_Day = 30;
            bool IsSameMonth = false, IsSameYear = false;

            var model = _accessGeneralRepository.FirstOrDefault(c => c.Id > 0);
            if (model == null)
            {
                model = new AccessGeneral();
            }
            if (model.StartTime == null)
            {
                model.StartTime = Convert.ToDateTime(accessData.VisitTime.ToString("yyyy-MM-dd"));
            }
            if (model.LastVisitedDate.HasValue)
            {
                var lastVisitedDate = model.LastVisitedDate.Value;
                var span = accessData.VisitTime.Subtract(lastVisitedDate);
                span_Day = (int)Math.Floor(span.TotalDays);
                IsSameMonth = accessData.VisitTime.Year == lastVisitedDate.Year && accessData.VisitTime.Month == lastVisitedDate.Month;
                IsSameYear = accessData.VisitTime.Year == lastVisitedDate.Year;
            }

            #region 统计今天与昨天的数据

            if (span_Day == 0)
            {
                //上条记录是今天的
                model.Pv_Today += 1;
                if (accessData.IsFistPage == 1)
                {
                    model.Uv_Today += 1;
                    model.Tiaochu_Today += 1;
                    model.Ip_Today = GetIpCount(model.LastVisitedDate.Value);
                }
                if (accessData.IsNewVisitor == 1)
                {
                    model.New_Today += 1;
                }
                if (minus != 0)
                {
                    model.Tiaochu_Today = model.Tiaochu_Today - minus;
                    if (model.Tiaochu_Today < 0)
                    {
                        model.Tiaochu_Today = 0;
                    }
                }
                model.LengthOfStay_Today = model.LengthOfStay_Today + lengthOfStay;
            }
            else if(span_Day == 1)
            {
                //上条记录是昨天的
                model.LastVisitedDate = Convert.ToDateTime(accessData.VisitTime.ToString("yyyy-MM-dd"));
                model.Pv_Yestoday = model.Pv_Today;
                model.Uv_Yestoday = model.Uv_Today;
                model.New_Yestoday = model.New_Today;
                model.Tiaochu_Yestoday = model.Tiaochu_Today;
                model.LengthOfStay_Yestoday = model.LengthOfStay_Today;
                model.Ip_Yestoday = model.Ip_Today;

                AddNewDateRecord(accessData, model, lengthOfStay);
            }
            else if(span_Day < 0)
            {
                //放弃不统计
            }
            else
            {
                //上条记录是若干天前的
                model.LastVisitedDate = Convert.ToDateTime(accessData.VisitTime.ToString("yyyy-MM-dd"));
                model.Pv_Yestoday = 0;
                model.Uv_Yestoday = 0;
                model.New_Yestoday = 0;
                model.Tiaochu_Yestoday = 0;
                model.LengthOfStay_Yestoday = 0;
                model.Ip_Yestoday = 0;

                AddNewDateRecord(accessData, model, lengthOfStay);
            }

            #endregion

            #region 当月的数据

            if (IsSameMonth)
            {  //本月
                model.Pv_Month += 1;
                if (accessData.IsFistPage == 1)
                {
                    model.Uv_Month += 1;
                    model.Tiaochu_Month += 1;
                    var date = model.LastVisitedDate.Value;
                    model.Ip_Month = GetIpCount(date.AddDays(1 - date.Day));
                }
                if (accessData.IsNewVisitor == 1)
                {
                    model.New_Month += 1;
                }
                if (minus != 0)
                {
                    model.Tiaochu_Month = model.Tiaochu_Month - minus;
                    if (model.Tiaochu_Month < 0)
                    {
                        model.Tiaochu_Month = 0;
                    }
                }
                model.LengthOfStay_Month = model.LengthOfStay_Month + lengthOfStay;
            }
            else
            {
                //上一条数据是若干月前                            
                model.Pv_Month = 1;
                model.Uv_Month = 1;
                model.Ip_Month = 1;
                if (accessData.IsNewVisitor == 1)
                {
                    model.New_Month = 1;
                }
                else
                {
                    model.New_Month = 0;
                }
                model.LengthOfStay_Month = lengthOfStay;
                if (accessData.IsFistPage == 1)
                {
                    model.Tiaochu_Month = 1;
                }
                else
                {
                    model.Tiaochu_Month = 0;
                }
            }
            #endregion

            #region 今年的数据

            if (IsSameYear)
            {
                model.Pv_Year += 1;
                if (accessData.IsFistPage == 1)
                {
                    model.Uv_Year += 1;
                    model.Tiaochu_Year += 1;
                    var date = model.LastVisitedDate.Value;
                    model.Ip_Year = GetIpCount(date.AddDays(1 - date.Day).AddMonths(1 - date.Month));
                }
                if (accessData.IsNewVisitor == 1)
                {
                    model.New_Year += 1;
                }
                if (minus != 0)
                {
                    model.Tiaochu_Year = model.Tiaochu_Year - minus;
                    if (model.Tiaochu_Year < 0)
                    {
                        model.Tiaochu_Year = 0;
                    }
                }
                model.LengthOfStay_Year = model.LengthOfStay_Year + lengthOfStay;
            }
            else
            {
                //上一条数据是若干年前
                model.Pv_Year = 1;
                model.Uv_Year = 1;
                model.Ip_Year = 1;
                if (accessData.IsNewVisitor == 1)
                {
                    model.New_Year = 1;
                }
                else
                {
                    model.New_Year = 0;
                }
                model.LengthOfStay_Year = lengthOfStay;
                if (accessData.IsFistPage == 1)
                {
                    model.Tiaochu_Year = 1;
                }
                else
                {
                    model.Tiaochu_Year = 0;
                }
            }
            #endregion

            #region 总数

            model.Pv_TotolNum += 1;
            if (accessData.IsFistPage == 1)
            {
                model.Uv_TotolNum += 1;
                model.Tiaochu_TotolNum += 1;
                model.Ip_TotolNum = GetIpCount(model.StartTime.Value);
            }
            if (accessData.IsNewVisitor == 1)
            {
                model.New_TotolNum += 1;
            }
            if (minus != 0)
            {
                model.Tiaochu_TotolNum = model.Tiaochu_TotolNum - minus;
                if (model.Tiaochu_TotolNum < 0)
                {
                    model.Tiaochu_TotolNum = 0;
                }
            }
            model.LengthOfStay_Totol = model.LengthOfStay_Totol + lengthOfStay;
            #endregion

            #region 历史最高

            if (model.Pv_Today > model.Pv_Max_InADay)
            {
                model.Pv_Max_InADay = model.Pv_Today;
                model.Pv_Max_InADay_Date = model.LastVisitedDate;
            }
            if (model.Uv_Today > model.Uv_Max_InADay)
            {
                model.Uv_Max_InADay = model.Uv_Today;
                model.Uv_Max_InADay_Date = model.LastVisitedDate;
            }
            if (model.Ip_Today > model.Ip_Max_InADay)
            {
                model.Ip_Max_InADay = model.Ip_Today;
                model.Ip_Max_InADay_Date = model.LastVisitedDate;
            }
            if (model.Tiaochu_Today > model.Tiaochu_Max_InADay)
            {
                model.Tiaochu_Max_InADay = model.Tiaochu_Today;
                model.Tiaochu_Max_InADay_Date = model.LastVisitedDate;
            }
            if (model.New_Today > model.New_Max_InADay)
            {
                model.New_Max_InADay = model.New_Today;
                model.New_Max_InADay_Date = model.LastVisitedDate;
            }
            if (model.LengthOfStay_Today > model.LengthOfStay_Max_InADay)
            {
                model.LengthOfStay_Max_InADay = model.LengthOfStay_Today;
                model.LengthOfStay_Max_InADay_Date = model.LastVisitedDate;
            }
            #endregion

            if (model.IsTransient())
            {
                _accessGeneralRepository.Insert(model);
            }
            else
            {
                _accessGeneralRepository.Update(model);
            }
        }

        private static void AddNewDateRecord(AccessData accessData, AccessGeneral model,int lengthOfStay)
        {
            model.Pv_Today = 1;
            model.Uv_Today = 1;
            model.Ip_Today = 1;
            if (accessData.IsNewVisitor == 1)
            {
                model.New_Today = 1;
            }
            else
            {
                model.New_Today = 0;
            }
            model.LengthOfStay_Today = lengthOfStay;
            if (accessData.IsFistPage == 1)
            {
                model.Tiaochu_Today = 1;
            }
            else
            {
                model.Tiaochu_Today = 0;
            }
        }
        private int GetIpCount(DateTime beginTime)
        {
            var temp = from d in _accessDataRepository.GetAll()
                       where d.VisitTime >= beginTime
                       group d by d.ClientIp into g
            select g.Key;

            return temp.Count();
        }

        private async Task<AccessData> TranslateFromArgs(VisitorArgs args, ClientInfo client)
        {
            var accessData = new AccessData();
            accessData.IsNewVisitor = args.IsNewVisitor ? 1 : 0;
            accessData.Acount = args.HMAccount;
            accessData.ClientIp = args.ClientIp;
            accessData.UserAgent = args.UserAgent;
            accessData.VisitTime = args.VisitTime;
            accessData.CookieEnabled = args.CookieEnabled;
            accessData.Resolution = args.Resolution;
            accessData.ColorDepth = args.ColorDepth;
            accessData.WindowHeight = args.WindowHeight;
            accessData.CultureName = args.CultureName;
            accessData.Title = args.Title;
            accessData.IsExternalLinks = args.RefererUrlIsExternal ? 1 : 0;

            var visitTime = args.VisitTime;
            accessData.Year = visitTime.Year;
            accessData.Month = visitTime.Month;
            accessData.Day = visitTime.Day;
            accessData.Hour = visitTime.Hour;
            accessData.Week = visitTime.DayOfWeek.RawValue();
            accessData.SundayFormart = visitTime.AddDays(-1 * accessData.Week).ToString("yyyy-MM-dd");
            accessData.DayFormart = visitTime.ToString("yyyy-MM-dd");
            accessData.MonthFormart = visitTime.ToString("yyyy-MM");

            GetInfoFromUserAgent(accessData, client);

            accessData.Url = args.Url;
            await GetInfoFromPageUrl(accessData);

            accessData.RefererUrl = args.RefererUrl;

            return accessData;
        }

        private static void CopyDataFromPreData(AccessData preData, AccessData accessData)
        {
            accessData.FrequentVisits = preData.FrequentVisits;
            accessData.LastVisitTime = preData.LastVisitTime;
            accessData.FirstPageUrl = preData.FirstPageUrl;
            accessData.FirstPageVisitTime = preData.FirstPageVisitTime;

            accessData.SourceType = preData.SourceType;
            accessData.IsExternalLinks = preData.IsExternalLinks;
            accessData.IsSearchEngines = preData.IsSearchEngines;
            accessData.EngineName = preData.EngineName;
            accessData.Searchkey = preData.Searchkey;
            accessData.EngineType = preData.EngineType;

            accessData.ISP = preData.ISP;
            accessData.Region = preData.Region;
            accessData.City = preData.City;
        }

        private static int CompletePreData(AccessData preData, TimeSpan span)
        {
            preData.LengthOfStay = span.TotalMilliseconds.To<int>();
            preData.TimeTotol = preData.TimeTotol + preData.LengthOfStay;
            preData.IsBounced = 0;
            preData.IsEndPage = 0;

            return preData.LengthOfStay;
        }

        private async Task GetInfoFromPageUrl(AccessData accessData)
        {
            //获取租户id, 栏目id等
            // 1. 重写回真实链接
            var data = await _routeDataChecker.RewriteAndGetRouteDataByUrlAsync(accessData.Url);
            if(data != null)
            {
                accessData.RouteData = data.Values.ToJsonString();
                var tenancyName = data.Values["tenancyName"];
                var kind = data.Values["kind"];
                if(tenancyName != null && kind != null)
                {
                    var tenant = _tenantStore.Find(tenancyName.ToString());
                    long auxiliaryId;
                    if (tenant != null && long.TryParse(kind.ToString(),out auxiliaryId))
                    {
                        accessData.TenancyName = tenant.TenancyName;
                        var idItem = await _menuCache.GetMenuIdTraceCacheItemAsync(tenant.Id, auxiliaryId);
                        accessData.Lv1MenuId = idItem.IdTrace.FirstOrDefault();
                        accessData.MenuId = idItem.Id();
                    }
                }
            }
        }

        private static void GetInfoFromUserAgent(AccessData accessData, ClientInfo client)
        {
            accessData.Device = client.Device.ToString();
            accessData.FormFactor = client.Device.FormFactor;
            accessData.OSFamily = client.OS.Family;
            accessData.OS = client.OS.ToString();
            accessData.BrowserFamily = client.UA.Family;
            accessData.Browser = client.UA.ToString();
        }

        private static void GetInfoFromRefererUrl(AccessData accessData)
        {
            accessData.SourceType = SourceType.Directaccess;

            //判断页面打开方式，搜索引擎
            if (!accessData.RefererUrl.IsNullOrEmpty())
            {
                var result = SearchEngineHelper.GetSearchEngines(accessData.RefererUrl);
                if(result.IsSearchEngines)
                {
                    accessData.IsSearchEngines = 1;           //是搜索引擎
                    accessData.IsExternalLinks = 0;           //不是外部链接
                    accessData.SourceType = SourceType.SearchEngines;
                    accessData.Searchkey = result.Searchkey;
                    accessData.EngineName = result.EngineName;
                    accessData.EngineType = result.EngineType;
                }
            }

            if (accessData.IsExternalLinks == 1)
            {
                accessData.SourceType = SourceType.ExternalLinks;
            }
        }
    }
}
